// OpenCL restrictions:
// https://registry.khronos.org/OpenCL/specs/opencl-1.2.pdf (section 6.9)


#include "opencl.h"


double const PI = 3.14159265358979323846;
float const SPEED_MULTIPLIER = 0.5;


__kernel void move_droplet(
		double const time_delta,
		float const screen_height,
		float const friction,
		__global Droplet * droplets,
		uint droplet_count) {
	size_t idx = get_global_id(0);
	size_t droplets_per_work_item = get_items_per_work_item(droplet_count);
	size_t start_i = idx * droplets_per_work_item;
	size_t end_i = \
		min(droplet_count, (uint) (start_i + droplets_per_work_item));
	
	for (size_t i = start_i; i < end_i; i++) {
		// Move droplet if it overcomes friction
		droplets[i].y += \
				PI * pow(droplets[i].radius, 2.0f) * SPEED_MULTIPLIER
				* (droplets[i].radius > friction)
				* time_delta;
	}
}


__kernel void merge_overlapping_droplets(
		__global Droplet * droplets,
		uint const droplet_count,
		__global int * droplets_to_erase) {
	uint total_iters = \
		((droplet_count - 1) * (droplet_count - 1) + droplet_count - 1) / 2;
	uint iters_per_work_item = get_items_per_work_item(total_iters);
	
	uint i, j;
	uint * y = &i;
	uint * x = &j;
	start_of_merge_overlapping_droplets(
		droplet_count, total_iters, &y, &x);
	if (y == NULL || x == NULL) {
		return;
	}
	
	uint iters_done = 0;
	for ( ; i < droplet_count - 1; i++) {
		for ( ; j < droplet_count; j++) {
			Droplet * dropi = &(droplets[i]);
			Droplet * dropj = &(droplets[j]);
			float displacement[2] = {dropj->x - dropi->x, dropj->y - dropi->y};
			float distance = \
				sqrt(pow(displacement[0], 2) + pow(displacement[1], 2));
			float radius_sum = dropi->radius + dropj->radius;
			float size_ratio = dropj->radius / (dropi->radius + dropj->radius);
			if (distance < radius_sum) {
				float max_radius = max(dropi->radius, dropj->radius);
				float min_radius = min(dropi->radius, dropj->radius);
				dropi->radius = max_radius + min_radius / max_radius;
				dropi->x += displacement[0] * size_ratio;
				// use `max` to prevent droplet from moving upwards
				dropi->y += max(0.0f, displacement[1] * size_ratio);
				droplets_to_erase[j] = 1;
			}
			
			iters_done++;
			if (iters_done == iters_per_work_item) {
				goto merge_loops_end;
			}
		}
		j = i + 2;
	}
	merge_loops_end:
	return;
}


void start_of_merge_overlapping_droplets(
		uint const droplet_count,
		uint const total_iters,
		uint ** i,
		uint ** j) {
	/*
	Sets `i` and `j` to the starting coordinate for the current work item. If
	the current work item should not do any processing then `i` and `j` will be
	set to `NULL`
	*/
	size_t idx = get_global_id(0);
	uint iters_per_work_item = get_items_per_work_item(total_iters);
	uint cell_number = idx * iters_per_work_item;
	
	uint y, x;
	uint * i_start = &y;
	uint * j_start = &x;
	cell_number_to_coordinate(cell_number, droplet_count - 1, &j_start,
			&i_start);
	if (j_start == NULL || i_start == NULL) {
		*i = NULL;
		*j = NULL;
	}
	else {
		// Increase `j_start` by 1 so that it is always at least 1 greater than
		// `i_start`. This prevents comparing an element with its self
		(*j_start)++;
		
		**i = *i_start;
		**j = *j_start;
	}
}


void cell_number_to_coordinate(
		uint const cell_number, uint const width, uint ** x, uint ** y) {
	/*
	Converts a cell number into coordinates, where the grid is of equal height
	and width and every row has a width 1 less than the row below it, forming a
	right angled triangle.

	Example of a grid with width == 3:
	2|       5
	1|    3  4
	0| 0  1  2
	  --------
	   0  1  2

	Args:
		cell_number: number of the cell to get the coordinates of
		width: width (and height) of the grid of cells
		x: variable the x coordinate will be stored in. `NULL` if `cell_number`
			is invalid for the given `width`
		y: variable the y coordinate will be stored in. `NULL` if `cell_number`
			is invalid for the given `width`
	*/
	if (cell_number < 0 || cell_number >= (width * width + width) / 2) {
		// `cell_number` is out of range for the given `width`
		*x = NULL;
		*y = NULL;
		return;
	}
	
	uint j = 0;
	uint i = 0;
	uint countdown = cell_number;
	while (countdown > 0) {
		j++;
		if (j >= width) {
			i++;
			j = i;
		}
		countdown--;
	}
	
	**x = j;
	**y = i;
}


uint get_items_per_work_item(uint const item_count) {
	uint items_per_work_item = \
		max(1u, (uint) (item_count / get_global_size(0)));
	if (get_global_size(0) * items_per_work_item < item_count) {
		items_per_work_item++;
	}
	return items_per_work_item;
}
